package ru.ifmo.ct.network.brafs.node;

import ru.ifmo.ct.network.brafs.IllegalCommandFormatException;
import ru.ifmo.ct.network.brafs.Utils;
import ru.ifmo.ct.network.brafs.file.FileHolder;

import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.InetAddress;
import java.net.Socket;
import java.nio.ByteBuffer;
import java.util.Arrays;

/**
 * @author Alex Vikharev vikharev@yandex-team.ru
 *         created 18.04.12
 */
public class CommandDispatcher implements Runnable {

    private final FileHolder holder;
    private final Socket client;

    public CommandDispatcher(final FileHolder holder, final Socket client) {
        this.holder = holder;
        this.client = client;
    }

    @Override
    public void run() {
        while (!Thread.interrupted()) {
            final Command command;
            try {
                command = Command.read(client.getInputStream());
            } catch (IOException | IllegalCommandFormatException e) {
//                System.err.println("Error reading command: " + e.getLocalizedMessage());
                return;
            }

            final String fileName = command.getFileName();
            final long segmentId = command.getSegmentId();

            final byte[] ip = command.getIp();
            final int port = command.getPort();

            switch (command.getType()) {
                case DELETE_SEGMENT:
                    holder.delete(fileName, segmentId);
                    System.err.println("Deleted segment " + segmentId + " from file " + fileName);
                    break;
                case GET_SEGMENT:
                    sendSegment(fileName, segmentId);
                    break;
                case CHECK_SEGMENT:
                    sendHash(fileName, segmentId);
                    break;
                case GET_SEGMENT_FROM:
                    fetchSegment(fileName, segmentId, ip, port);
                    break;
                default:
                    System.err.println("Unsupported command: " + command.getType());
            }
        }
    }

    private void fetchSegment(final String fileName, final long segmentId, final byte[] ip, final int port) {
        try {
            try (Socket socket = new Socket(InetAddress.getByAddress(ip), port)) {
                final Command getCommand = new Command(CommandType.GET_SEGMENT, segmentId, fileName);
                getCommand.write(socket.getOutputStream());
                final InputStream inputStream = socket.getInputStream();

                final ByteBuffer buffer = Utils.readSegment(inputStream);
                final byte[] data = buffer.array();
                holder.put(fileName, segmentId, data);
                System.err.println("Fetched segment " + segmentId + " from file " + fileName + " from ip " + Arrays.toString(ip) + " port " + port);
            }
        } catch (IOException e) {
            System.err.println("Error connecting to client: " + Arrays.toString(ip) + " port: " + port + " " + e.getLocalizedMessage());
        }
    }

    private void sendHash(final String fileName, final long segmentId) {
        final int hash = holder.hash(fileName, segmentId);
        final ByteBuffer buffer = ByteBuffer.allocate(4);
        buffer.putInt(hash);
        try {
            final OutputStream outputStream = client.getOutputStream();
            outputStream.write(buffer.array());
            System.err.println("Sent segment hash " + segmentId + " from file " + fileName + " hash: " + hash);
        } catch (IOException e) {
            System.err.println("Error write response: " + e.getLocalizedMessage());
        }
    }

    private void sendSegment(final String fileName, final long segmentId) {
        final byte[] bytes = holder.get(fileName, segmentId);
        if (bytes != null) {
            final ByteBuffer buffer = ByteBuffer.allocate(bytes.length + 4);
            buffer.putInt(bytes.length);
            buffer.put(bytes);
            try {
                final OutputStream outputStream = client.getOutputStream();
                outputStream.write(buffer.array());
                System.err.println("Sent segment " + segmentId + " from file " + fileName);
            } catch (IOException e) {
                System.err.println("Error write response: " + e.getLocalizedMessage());
            }
        }
    }

}
